package opn.srn.common.script;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

public class ScriptExecutorFactory {
	final static Map<String, Map<String, ObjectPool<CompiledScript>>> scriptCache = new ConcurrentHashMap<String, Map<String, ObjectPool<CompiledScript>>>();
	final static Map<String, String> defaultScriptPrefix = new ConcurrentHashMap<String, String>();

	final static ScriptEngineManager scriptManager = new ScriptEngineManager();

	final static String const_MAP_ScriptType = "map";

	public static Object executeScript(ScriptInfo scriptInfo, Bindings bindings) throws Exception {
		return executeScript(scriptInfo, bindings, true);
	}

	public static Object executeScript(ScriptInfo scriptInfo, Bindings bindings, boolean useDefaults) throws Exception {
		String scriptCode = useDefaults ? getCode(scriptInfo) : scriptInfo.getCode();
		String scriptType = scriptInfo.getType();

		if (scriptType.equals(const_MAP_ScriptType)) {
			return bindings.get(scriptCode);
		}

		Map<String, ObjectPool<CompiledScript>> compiledScriptCache = scriptCache.get(scriptType);
		if (compiledScriptCache == null) {
			synchronized (scriptCache) {
				if (compiledScriptCache == null) {
					compiledScriptCache = new ConcurrentHashMap<String, ObjectPool<CompiledScript>>();
					scriptCache.put(scriptType, compiledScriptCache);
				}
			}
		}

		ObjectPool<CompiledScript> compiledScriptPool = compiledScriptCache.get(scriptCode);
		if (compiledScriptPool == null) {
			synchronized (scriptCache) {
				if (compiledScriptPool == null) {
					compiledScriptPool = new CompiledScriptPool();
					compiledScriptCache.put(scriptCode, compiledScriptPool);
				}
			}
		}

		CompiledScript compiledScript = compiledScriptPool.getObject();
		if (compiledScript == null) {
			ScriptEngine scriptEngine = scriptManager.getEngineByExtension(scriptType);
			if (scriptEngine == null) {
				throw new Exception("Script engine not available for extension : " + scriptType);
			}
			if (scriptEngine instanceof Compilable == false) {
				throw new Exception("Script engine doesn't support pre-compilable option");
			}

			compiledScript = ((Compilable) scriptEngine).compile(scriptCode);
		}

		Object returnValue = compiledScript.eval(bindings);
		compiledScriptPool.release(compiledScript);
		return returnValue;
	}

	private static String getCode(ScriptInfo scriptInfo) {
		String prefix = defaultScriptPrefix.get(scriptInfo.getType());
		return (prefix == null) ? scriptInfo.getCode() : prefix + scriptInfo.getCode();
	}

	public static ScriptInfo getScriptInfo(String script) throws Exception {
		Pattern pattern = Pattern.compile("^(\\w+):\\/\\/\\{(.+)\\}$", Pattern.DOTALL);
		Matcher matcher = pattern.matcher(script.trim());
		if (matcher.find() == false || matcher.groupCount() != 2) {
			throw new Exception("Invalid script code.  valid syntax: \nscripttype://{code} \n eg. groovy://{field1}");
		}

		return new ScriptInfo(matcher.group(1), matcher.group(2));
	}

	public static void addScriptPrefix(String scriptType, String prefixCode) {
		defaultScriptPrefix.put(scriptType, prefixCode);
	}

	public static void appendScriptPrefix(String scriptType, String prefixCode) {
		String previousValue = defaultScriptPrefix.get(scriptType);
		if (previousValue != null) {
			defaultScriptPrefix.put(scriptType, previousValue + prefixCode);
		} else {
			defaultScriptPrefix.put(scriptType, prefixCode);
		}
	}

	public static void main(String[] args) throws Exception {
		ScriptInfo scriptInfo = getScriptInfo("groovy://{sample code\r\n\nint i = 0;}");
		System.out.println("Script type : [" + scriptInfo.getType() + "]\n Script code : [" + scriptInfo.getCode()
				+ "]");
	}

	private static class CompiledScriptPool extends ObjectPool<CompiledScript> {

		@Override
		protected CompiledScript newObject() {
			return null;
		}

		@Override
		protected CompiledScript resetObject(CompiledScript object) {
			return object;
		}

	}

}
